using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace Flood.FloodStrategies
{
	public sealed class FloodAdjacentColorThatHasTheMostDrySquaresRecurseToBreakTies : FloodStrategyBase,
	                                                                                   IFloodStrategy
	{
		private readonly int delayCount;
		private readonly Dictionary<string, ResultContainer> memory;
		private readonly int minValue;
		private readonly PuzzleConstants puzzleConstants;
		private char? firstColorToReturn;

		public FloodAdjacentColorThatHasTheMostDrySquaresRecurseToBreakTies(int minValue, int remove,
		                                                                    char? firstColorToReturn,
		                                                                    PuzzleConstants puzzleConstants)
			: this(minValue, remove, firstColorToReturn, puzzleConstants, new Dictionary<string, ResultContainer>())
		{
		}

		private FloodAdjacentColorThatHasTheMostDrySquaresRecurseToBreakTies(int minValue, int delayCount,
		                                                                     char? firstColorToReturn,
		                                                                     PuzzleConstants puzzleConstants,
		                                                                     Dictionary<string, ResultContainer>
		                                                                     	memory)
		{
			this.memory = memory;
			this.minValue = minValue;
			this.delayCount = delayCount;
			this.firstColorToReturn = firstColorToReturn;
			this.puzzleConstants = puzzleConstants;
		}

		#region IFloodStrategy Members

		public Result GetNextToFlood(IEnumerable<Shape> moistShapes, Grid grid)
		{
			if (firstColorToReturn != null)
			{
				char color = firstColorToReturn.Value;
				firstColorToReturn = null;
				return new Result(color, false);
			}
			var byDescending = moistShapes
				.Select(shape => new { Count = CountDryAdjacentSquares(shape), Shape = shape })
				.OrderByDescending(item => item.Count);
			var first = byDescending.First();
			if (first.Count == 0)
			{
				DisplayMessage("no more blocked squares...");
				// there are no more blocked squares so the solution
				// order for the remaining squares doesn't matter
				return new Result(moistShapes.Select(shape => shape.Color).OrderByDescending(x => x).ToList(), true);
			}
			int tieQualifyingCount = first.Count;
			var best = byDescending.Where(item => item.Count >= tieQualifyingCount || item.Count >= minValue).ToList();

			if (best.Count == 1)
			{
				DisplayMessage("only one really good result...");
				return new Result(first.Shape.Color, false);
			}

			string gridKey = grid.Encode();

			int nextValue = minValue;
			int nextDelay;
			if (delayCount == 1)
			{
				nextValue = minValue + 1;
				nextDelay = 1;
			}
			else
			{
				nextDelay = delayCount - 1;
			}

			ResultContainer previousResult;
			if (memory.TryGetValue(gridKey, out previousResult))
			{
				if (previousResult.MinValue <= nextValue)
				{
					return previousResult.Result;
				}
			}

			DisplayMessage("branching among " + best.Count + " possibilities...");
			// we have multiple equivalent results, try them all and keep the best.
			return
				best.Select(item => Recurse(item.Shape.Color, grid, gridKey, nextValue, nextDelay)).OrderBy(
					item => item.FillSteps.Count).First();
		}

		#endregion

		[Conditional("DEBUG")]
		[Conditional("DETAILED")]
		private void DisplayMessage(string message)
		{
			if (puzzleConstants.DisplayMoves)
			{
				Console.WriteLine(message);
			}
		}

		private Result Recurse(char color, Grid grid, string gridKey, int nextValue, int nextDelay)
		{
			grid.Checkpoint();
			try
			{
				Puzzle puzzle = new Puzzle(grid, puzzleConstants);

				Result result = puzzle.Flood(new FloodAdjacentColorThatHasTheMostDrySquaresRecurseToBreakTies(nextValue,
				                                                                                              nextDelay,
				                                                                                              color,
				                                                                                              puzzleConstants,
				                                                                                              memory),
				                             puzzleConstants.MaxFloods);
				ResultContainer previousResult;
				if (memory.TryGetValue(gridKey, out previousResult))
				{
					if (previousResult.Result.FillSteps.Count <= result.FillSteps.Count)
					{
						if (previousResult.MinValue < nextValue)
						{
							previousResult.MinValue = nextValue;
						}
						return previousResult.Result;
					}
				}

				if (previousResult == null)
				{
					previousResult = new ResultContainer();
					memory.Add(gridKey, previousResult);
				}

				previousResult.MinValue = nextValue;
				previousResult.Result = result;
				return result;
			}
			finally
			{
				grid.Undo();
			}
		}
	}

	public class ResultContainer
	{
		public int MinValue { get; set; }
		public Result Result { get; set; }
	}
}